<?php

namespace app\api\service\common;


use app\api\service\common\token\JwtTokenService;
use app\common\Constants;
use app\common\exception\BusinessException;
use app\common\facade\HttpHelper;
use app\common\service\CaptchaService;
use app\common\service\VerificationCodeService;
use think\facade\Cache;
use think\facade\Db;
use think\Request;
use app\api\model\web\Member as MemberModel;

class AuthCheckService
{
    public const AUTH_TYPE_JWT = 'jwt';
    public const AUTH_TYPE_SMS = 'sms';
    public const AUTH_TYPE_CAPTCHA = 'captcha';
    public const API_AUTH_CACHE_EXPIRED = 3600;

    /**
     * 认证类型与数据库字段的映射
     * @var array|string[]
     */
    private array $authColumnMap = [
        self::AUTH_TYPE_JWT => 'auth_jwt',
        self::AUTH_TYPE_SMS => 'auth_sms',
        self::AUTH_TYPE_CAPTCHA => 'auth_img',
    ];

    public function __construct(
        protected JwtTokenService         $tokenService,
        protected VerificationCodeService $verificationCodeService,
    )
    {
    }

    /**
     * 方案 1
     */
    public function check(Request $request, string $url)
    {
        // 1. 一次性获取所有白名单
        $allWhitelists = [];
        foreach ($this->authColumnMap as $authType => $authColumn) {
            $cacheKey = $this->getWhitelistCacheKey($authType);
            $whitelist = Cache::get($cacheKey);
            // 缓存不存在，则查询数据库 (开发阶段不使用缓存)
            if ($whitelist === null || app()->isDebug()) {
                // 如果缓存不存在，则查出配置白名单和数据库白名单，并缓存
                $configWhitelist = config('api.white_list');
                $dbWhitelist = Db::name('dev_action')
                    ->where('app', '=', 'api')
                    ->where('status', '=', 1)
                    ->where($authColumn, '=', 0)
                    ->whereNull('delete_time')
                    ->column('url');
                $whitelist = array_merge($dbWhitelist, $configWhitelist);
                Cache::set($cacheKey, $whitelist, self::API_AUTH_CACHE_EXPIRED);
            }
            $allWhitelists[$authType] = $whitelist;
        }
        //trace($url,'检查URL:');
        //trace($allWhitelists,'白名单列表:');


        // 2. 根据URL和白名单判断并执行认证
        if(in_array($url, $allWhitelists[self::AUTH_TYPE_JWT] ?? [])){
            trace('存在于白名单中，不需要JWT验证', 'Debug');
        } else {
            trace('需要JWT验证', 'Debug');
            $this->checkJwtInternal($request);
        }

        if (in_array($url, $allWhitelists[self::AUTH_TYPE_SMS] ?? [])) {
            trace('存在于白名单中，不需要SMS验证', 'Debug');
        } else {
            trace('需要SMS验证', 'Debug');
            $this->checkSmsInternal($request);
        }

        if (in_array($url, $allWhitelists[self::AUTH_TYPE_CAPTCHA] ?? [])) {
            trace('存在于白名单中，不需要CAPTCHA验证', 'Debug');
        } else {
            trace('需要CAPTCHA验证', 'Debug:');
            $this->checkCaptchaInternal($request); // 内部方法
        }
    }

    /**
     * 检查JWT令牌
     * @param Request $request
     * @return void
     * @throws BusinessException
     */
    private function checkJwtInternal(Request $request): void
    {
        $token = HttpHelper::getBearerToken();
        if (empty($token)) {
            throw new BusinessException('Token不能为空');
        }
        $payload = $this->tokenService->validateToken($token);
        $payloadData = $payload['data'];
        // 将解析出的用户信息附加到请求对象上，方便后续控制器使用
        $fields = ['id', 'username', 'mobile', 'avatar', 'email', 'bio', 'nickname', 'status', 'sort'];
        $userInfo = MemberModel::field($fields)
            ->cache('userInfo_' . $payloadData->id, self::API_AUTH_CACHE_EXPIRED)
            ->find($payloadData->id);
        $request->userInfo = $userInfo;
    }

    /**
     * 检查短信验证码
     * @param Request $request
     * @return void
     * @throws BusinessException
     */
    private function checkSmsInternal(Request $request): void
    {
        $mobile = $request->param('mobile');
        $smsCode = $request->param('smsCode');

        if (empty($mobile)) throw new BusinessException('手机号不能为空');
        if (empty($smsCode)) throw new BusinessException('短信验证码不能为空');

        $key = Constants::CACHE_VERIFICATION_CODE_KEY . $mobile;

        $result = $this->verificationCodeService->verifyCode($key, $smsCode);

        if (!$result) {
            throw new BusinessException('短信验证码错误');
        }
    }

    /**
     * 检查图形验证码
     * @param Request $request
     * @return void
     * @throws BusinessException
     */
    private function checkCaptchaInternal(Request $request): void
    {
        $captchaCode = $request->param('captchaCode');
        $captchaKey = $request->param('captchaKey');
        if (empty($captchaCode) || empty($captchaKey)) {
            throw new BusinessException('图形验证码不能为空');
        }
        CaptchaService::checkCaptcha(['captchaCode' => $captchaCode, 'captchaKey' => $captchaKey]);
    }

    /**
     * 方案 2
     */

    /**
     * 检查JWT令牌
     * 如果URL不在白名单内，则执行检查
     * @param Request $request
     * @param string $url
     * @throws BusinessException
     */
    public function checkJwt(Request $request, string $url): void
    {
        $whitelist = $this->getAuthWhitelist(self::AUTH_TYPE_JWT);
        if (!in_array($url, $whitelist)) {
            $token = HttpHelper::getBearerToken();
            if (empty($token)) {
                throw new BusinessException('Token不能为空');
            }
            $payload = $this->tokenService->validateToken($token);
            // 将解析出的用户信息附加到请求对象上，方便后续控制器使用
            $request->userInfo = $payload['data'];
        }
    }

    /**
     * 检查短信验证码
     * 如果URL不在白名单内，则执行检查
     * @param Request $request
     * @param string $url
     * @throws BusinessException
     */
    public function checkSms(Request $request, string $url): void
    {
        $whitelist = $this->getAuthWhitelist(self::AUTH_TYPE_SMS);
        if (!in_array($url, $whitelist)) {
            $mobile = $request->param('mobile');
            $code = $request->param('code');

            if (empty($mobile)) throw new BusinessException('手机号不能为空');
            if (empty($code)) throw new BusinessException('短信验证码不能为空');

            $key = Constants::CACHE_VERIFICATION_CODE_KEY . $mobile;
            $this->verificationCodeService->verifyCode($key, $code);
        }
    }


    /**
     * 检查图形验证码
     * 如果URL不在白名单内，则执行检查
     * @param Request $request
     * @param string $url
     * @throws BusinessException
     */
    public function checkCaptcha(Request $request, string $url): void
    {
        $whitelist = $this->getAuthWhitelist(self::AUTH_TYPE_CAPTCHA);
        if (!in_array($url, $whitelist)) {
            $captchaCode = $request->param('captchaCode');
            $captchaKey = $request->param('captchaKey');
            if (empty($captchaCode) || empty($captchaKey)) {
                throw new BusinessException('图形验证码不能为空');
            }
            CaptchaService::checkCaptcha(['captchaCode' => $captchaCode, 'captchaKey' => $captchaKey]);
        }
    }

    /**
     * 获取指定认证类型的URL白名单 (核心逻辑)
     * @param string $authType 认证类型 (e.g., 'jwt', 'sms', 'img')
     * @return array
     */
    public function getAuthWhitelist(string $authType): array
    {
        if (app()->isDebug()) {
            $authColumn = $this->authColumnMap[$authType] ?? '';
            $whitelist = Db::name('dev_action')->where($authColumn, '=', 0)->column('url');
        } else {
            $cacheKey = $this->getWhitelistCacheKey($authType);
            $whitelist = Cache::get($cacheKey);
            if ($whitelist === null) {
                $authColumn = $this->authColumnMap[$authType] ?? '';
                if (empty($authColumn)) {
                    return [];
                }
                $whitelist = Db::name('dev_action')->where($authColumn, '=', 0)->column('url');
                Cache::set($cacheKey, $whitelist, self::API_AUTH_CACHE_EXPIRED);
            }
        }
        return $whitelist;
    }

    /**
     * 获取白名单的缓存键
     * @param string $authType
     * @return string
     */
    public function getWhitelistCacheKey(string $authType): string
    {
        return "api:auth_whitelist:{$authType}";
    }


    /**
     * 清除缓存
     * @return void
     */
    public function clearCache(): void
    {
        foreach ($this->authColumnMap as $authType => $authColumn) {
            $cacheKey = $this->getWhitelistCacheKey($authType);
            Cache::delete($cacheKey);
        }
    }

}
